package com.ess.common;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.util.Collection;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import com.ess.common.util.ClassUtil;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;

@ComponentScan
@Component
public class EssBaseService
{
    @Autowired
    RestTemplate client;

    ObjectMapper mapper = new ObjectMapper();

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate()
    {
        RestTemplate template = new RestTemplate();
        SimpleClientHttpRequestFactory factory = (SimpleClientHttpRequestFactory) template.getRequestFactory();
        factory.setConnectTimeout(3000);
        factory.setReadTimeout(3000);
        return template;
    }

    public <M> M get(Class<M> cls)
    {
        if (inOneJvm())
        {
            return newObject(cls);
        }
        return proxyObject(cls);
    }

    private boolean inOneJvm()
    {
        return false;
    }

    private <M> M newObject(Class<M> cls)
    {
        String clsName = cls.getName() + "Impl";
        try
        {
            @SuppressWarnings("unchecked")
            M m = (M) Class.forName(clsName).newInstance();
            return m;
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        return null;
    }

    private <M> M proxyObject(Class<M> cls)
    {
        EssServiceAnno anno = cls.getAnnotation(EssServiceAnno.class);
        final String basePath = anno.basePath();
        InvocationHandler handler = new InvocationHandler()
        {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
            {
                String searchStr = ClassUtil.searchParams(method, args);
                return restMethodCall(basePath, method, searchStr);
            }
        };
        @SuppressWarnings("unchecked")
        M proxy = (M) Proxy.newProxyInstance(cls.getClassLoader(), new Class[] { cls }, handler);
        return proxy;
    }

    private Object restMethodCall(final String basePath, Method method, String searchStr)
    {
        String url = basePath + "/" + method.getName() + "?" + searchStr;
        String response = client.getForObject(url, String.class);
        Class<?> returnType = method.getReturnType();
        Object ret = null;
        try
        {
            JavaType jt;
            if (Collection.class.isAssignableFrom(returnType))
            {
                ParameterizedType pt = (ParameterizedType) method.getGenericReturnType();
                Class<?> type = (Class<?>) pt.getActualTypeArguments()[0];
                jt = getCollectionType(returnType, type);
            }
            else if (Map.class.isAssignableFrom(returnType))
            {
                ParameterizedType pt = (ParameterizedType) method.getGenericReturnType();
                Class<?> keyType = (Class<?>) pt.getActualTypeArguments()[0];
                Class<?> valType = (Class<?>) pt.getActualTypeArguments()[1];
                jt = getCollectionType(returnType, keyType, valType);
            }
            else
            {
                jt = mapper.getTypeFactory().constructType(returnType);
            }
            if(returnType == Void.TYPE)
            {
                return null;
            }
            if(StringUtils.isEmpty(response))
            {
                return response;
            }
            return mapper.readValue(response, jt);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        return ret;
    }

    public JavaType getCollectionType(Class<?> collectionClass, Class<?>... elementClasses)
    {
        return mapper.getTypeFactory().constructParametricType(collectionClass, elementClasses);
    }
}
